package com.gotokeep.keep.composition.demo.samples;

import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.SurfaceTexture;
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Surface;
import android.view.TextureView;
import android.widget.Toast;

import com.gotokeep.keep.composition.demo.R;
import com.gotokeep.keep.composition.demo.source.SourceProvider;
import com.gotokeep.keep.composition.demo.widgets.ScalableTextureView;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;

public class PreviewActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener {
    private static final String TAG = PreviewActivity.class.getSimpleName();
    private MediaExtractor mediaExtractor;
    private MediaFormat mediaFormat;
    private MediaCodec decoder;
    private MediaCodec.BufferInfo decoderBufferInfo = new MediaCodec.BufferInfo();
    private Surface surface;
    private DecodeRunnable decodeRunnable;
    private Thread decodeThread;

    private ScalableTextureView previewView;
    private ScalableTextureView previewView2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_preview);

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
            }, 0);
        }

        previewView = findViewById(R.id.preview_view);
        previewView.setSurfaceTextureListener(this);
    }

    private void previewMedia(String filePath, SurfaceTexture surfaceTexture) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
            fail("系统版本需要 4.3 以上");
            return;
        }
        File mediaFile = new File(filePath);
        if (!mediaFile.exists()) {
            fail("文件不存在");
            return;
        }

        mediaExtractor = new MediaExtractor();
        try {
            mediaExtractor.setDataSource(filePath);
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }

        int videoTrackIndex = -1;
        for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {
            mediaFormat = mediaExtractor.getTrackFormat(i);
            String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
            if (mime.startsWith("video/")) {
                videoTrackIndex = i;
                break;
            }
        }

        if (videoTrackIndex == -1) {
            Log.e(TAG, "Can't find video track!");
        }

        mediaExtractor.selectTrack(videoTrackIndex);
        int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
        int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
        int rotation = getRotation(filePath);
        previewView.setVideoSize(width, height, rotation);
        int frameRate = mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);
        frameRate = (frameRate == 0) ? 24 : frameRate;

        String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
        try {
            decoder = MediaCodec.createDecoderByType(mime);
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        surface = new Surface(surfaceTexture);
        decoder.configure(mediaFormat, surface, null, 0);
        decoder.start();
        decodeRunnable = new DecodeRunnable(videoTrackIndex);
        decodeThread = new Thread(decodeRunnable);
        decodeThread.start();
    }

    private int getRotation(String path) {
        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
        try {
            retriever.setDataSource(path);
            String rotation = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
            return Integer.parseInt(rotation);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }

    private void fail(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT)
                .show();
        finish();
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        previewMedia(SourceProvider.VIDEO_SRC[0], surface);
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {

    }

    private class DecodeRunnable implements Runnable {
        private boolean decoding = false;
        private int videoIndex = -1;

        public DecodeRunnable(int videoIndex) {
            this.videoIndex = videoIndex;
        }

        public void stop() {
            decoding = false;
        }

        @Override
        public void run() {
            decoding = true;
            if (videoIndex >= 0) {
                long startMs = System.currentTimeMillis();
                while (decoding) {
                    boolean isVideoEnd = false;
                    int inputIndex = decoder.dequeueInputBuffer(1000);
                    if (inputIndex >= 0) {
                        ByteBuffer buffer = getInputBuffer(decoder, inputIndex);
                        buffer.clear();
                        mediaExtractor.selectTrack(videoIndex);
                        int ret = mediaExtractor.readSampleData(buffer, 0);
                        if (ret < 0) {
                            decoder.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                            isVideoEnd = true;
                        } else {
                            long videoTimestamp = mediaExtractor.getSampleTime();
                            decoder.queueInputBuffer(inputIndex, 0, ret, videoTimestamp, mediaExtractor.getSampleFlags());
                            mediaExtractor.advance();
                        }
                    }
                    int outputIndex = decoder.dequeueOutputBuffer(decoderBufferInfo, 1000);
                    switch (outputIndex) {
                        case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                            Log.d(TAG, "run: output buffers changed");
                            break;
                        case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                            Log.d(TAG, "run: output format changed");
                            break;
                        case MediaCodec.INFO_TRY_AGAIN_LATER:
                            Log.d(TAG, "run: Try later");
                            break;
                        default:
                            decoder.releaseOutputBuffer(outputIndex, true);
                            break;
                    }
                    while (decoderBufferInfo.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            break;
                        }
                    }
                    if (isVideoEnd) {
                        break;
                    }
                }
            }
            decoding = false;
            decoder.stop();
            decoder.release();
            mediaExtractor.release();
        }
    }

    private ByteBuffer getInputBuffer(MediaCodec decoder, int inputIndex) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            return decoder.getInputBuffer(inputIndex);
        } else {
            return decoder.getInputBuffers()[inputIndex];
        }
    }
}
